/** 
 * 
 */
package Verifier.ArithmeticSystem;

import java.math.BigInteger;

/**
 * This class represents a field element which is a number inside a field of some finite order.
 * 
 * @author Moshe Baavur
 */
public class FieldElement extends Element {
	// the field which contains the element
	Field containerField;
	// representation number of the element in the field
	BigInteger element;
	
	/**
	 * Creates a new field element. The field should contain this element, which means the element should be a number
	 * between 0 and the (field's order - 1).
	 * 
	 * @param element the number representation of the element in the field.
	 * @param containerField the field which contains the element.
	 * @throws Exception if the element is not a number between 0 and the (field's order - 1).
	 */
	public FieldElement(BigInteger element, Field containerField) throws Exception {
		this.element = element;
		
		// need to check for belonging to the field
		if (containerField.doesElementBelong(this)) {
			this.containerField = containerField;
		} else {
			throw new Exception("Field Element: The element " + element.toString() +" does not belong to the field with order " + containerField.getOrder().toString());
		}
	}
	
	/**
	 * Returns the element representation number in the field.
	 * This number is between 0 and the (field's order - 1).
	 * 
	 * @return the element representation number in the field. 
	 */
	public BigInteger getElement() {
		return (element);
	}
	
	/**
	 * Returns the field which contains this element.
	 * 
	 * @return the container field.
	 */
	public Field getField() {
		return (containerField);
	}
	
	/**
	 * Returns a new field element which is the result of adding the current element to the
	 * other element (both elements should be in the same field).
	 * 
	 * @param other the field element to add to this.
	 * @return its value is the result of adding the 2 elements in this field.
	 * @throws Exception if the elements, this and other, are from different fields.
	 */
	public FieldElement add(FieldElement other) throws Exception {
		// if the fields are not the same, we should throw an exception
		if (!containerField.equals(other.containerField)) {
			throw new Exception("Field Element: Add: can't add elements from different fields");
		}
		
		// add the big integers
		BigInteger result = element.add(other.element);
		// now keep the result inside the field using modulus
		result = result.mod(containerField.getOrder());
		
		// create the new field element with the field containing this and the result of the calculations
		FieldElement newElement = null;
		try {
			// this line should always succeed due to the modular calculation we did before, so the try-catch block is for java's safety check
			newElement = new FieldElement(result, containerField);
		} catch (Exception e) {}
		
		return (newElement);
	}
	
	/**
	 * Returns a new field element which is the result of adding the first element to the
	 * second element (both elements should be in the same field).
	 * 
	 * @param first the first field element to add.
	 * @param second the second field element to add.
	 * @return its value is the result of adding the 2 elements in this field.
	 * @throws Exception if the elements, first and second, are from different fields.
	 */
	public static FieldElement add(FieldElement first, FieldElement second) throws Exception {
		// use the instance method to add the elements
		return (first.add(second));
	}
	
	/**
	 * Returns a new field element which is the result of multiplying the current element with the
	 * other element (both elements should be in the same field).
	 * 
	 * @param other the field element to multiply with this.
	 * @return its value is the result of multiplying the 2 elements in this field.
	 * @throws Exception if the elements, this and other, are from different fields.
	 */
	public FieldElement multiply(FieldElement other) throws Exception {
		// if the fields are not the same, we should throw an exception
		if (!containerField.equals(other.containerField)) {
			throw new Exception("Field Element: Multiply: can't add elements from different fields");
		}
		
		// multiply the big integers
		BigInteger result = element.multiply(other.element);
		// now keep the result inside the field using modulus
		result = result.mod(containerField.getOrder());
		
		// create the new field element with the field containing this and the result of the calculations
		FieldElement newElement = null;
		try {
			// this line should always succeed due to the modular calculation we did before, so the try-catch block is for java's safety check
			newElement = new FieldElement(result, containerField);
		} catch (Exception e) {}
		
		return (newElement);
	}
	
	/**
	 * Returns a new field element which is the result of multiplying the first element with the
	 * second element (both elements should be in the same field).
	 * 
	 * @param first the first field element to multiply.
	 * @param second the second field element to multiply.
	 * @return its value is the result of multiplying the 2 elements in this field.
	 * @throws Exception if the elements, first and second, are from different fields.
	 */
	public static FieldElement multiply(FieldElement first, FieldElement second) throws Exception {
		// use the instance method to multiply the elements
		return (first.multiply(second));
	}
	
	/**
	 * Returns a new field element which is the result of calculating this^exponent.
	 * 
	 * @param exponent the exponent in the calculation.
	 * @return its value is the result of calculating this^exponent in this field.
	 */
	public FieldElement power(BigInteger exponent) {		
		// calculate this^exponent with the "modding" indcluded
		BigInteger result = element.modPow(exponent, containerField.getOrder());
		
		// create the new field element with the field containing this and the result of the calculations
		FieldElement newElement = null;
		try {
			// this line should always succeed due to the modular calculation we did before, so the try-catch block is for java's safety check
			newElement = new FieldElement(result, containerField);
		} catch (Exception e) {}
		
		return (newElement);
	}
	
	/**
	 * Returns a new field element which is the result of calculating base^exponent.
	 * 
	 * @param base the field element which is the base in the calculations.
	 * @param exponent the exponent in the calculation.
	 * @return its value is the result of calculating this^exponent in this field.
	 */
	public static FieldElement power(FieldElement base, BigInteger exponent) throws Exception {
		// use the instance method to add the elements
		return (base.power(exponent));
	}
	
	/**
	 * Compare this to obj. They are equal only if obj is of type FieldElement and their field and element are equal.
	 * 
	 * @param obj the object to compare to this.
	 * @return true if and only if obj is of type FieldElement and the field and the element are equal to this.
	 */
	@Override
	public boolean equals(Object obj) {
		// if the object is a FieldElement, then compare them using their element and fields and not the address
		if (obj instanceof FieldElement) {
			FieldElement objElement = (FieldElement)obj;
			
			return (containerField.equals(objElement.containerField) && element.equals(objElement.element));
		}
		// otherwise, they are not equal
		return (false);
	}
}
